
#include <BFont>
#include <BPainter>
#include <BImage>
#include <BKeyEvent>
#include <BMouseEvent>
#include <BCharEvent>
#include <BScrollBar>
#include <BSystem>
#include <BFile>

#include <BTextEdit>
#include "member_BTextEdit.h"
#include "member_BWidget.h"
#include "member_BFont.h"
#include "member_BTextFormat.h"

using namespace BWE;

#define member					(*(member_BTextEdit*)_ptr)
#define member_allocate()		_ptr = new member_BTextEdit(this)
#define member_release()		delete (member_BTextEdit*)_ptr

BTextEdit::BTextEdit(BWidget* parent) : BScrollArea(parent)
{
	member_allocate();
	member.initBlocks();
}
BTextEdit::~BTextEdit()
{
	member_release();
}

bool BTextEdit::load(const BString& fileName)
{
	BFile file(fileName);
	if (!file.open(IO_Read))
		return false;
	member.text.reset((char)0, file.size());
	member.text.reset();
	char code = 0;
	while (file.read(code))
	{
		member.text.append(code);
	}
	member.initBlocks();
	member.dirty = true;
	this->fresh();
	return true;
}
bool BTextEdit::save(const BString& fileName) const
{
	BFile file(fileName);
	if (!file.open(IO_Write))
		return false;
	const BString& text = this->text();
	return file.write(text.cstr(), text.size());
}
void BTextEdit::clear()
{
	if (member.blocklst.size())
	{
		member.text.clear();
		member.blocklst.clear();
		member.changed = false;
		member.showBegin.reset();
		member.showEnd.reset();
		member.dirty = true;
		this->fresh();
	}
}

void BTextEdit::setLineWrap(bool lineWrap)
{
	if (member.lineWrap != lineWrap)
	{
		member.lineWrap = lineWrap;
		for (int i = 0; i < member.blocklst.size(); i++)
		{
			member.blocklst[i].dirtyCells = true;
		}
		member.dirty = true;
		this->fresh();
	}
}
bool BTextEdit::lineWrap() const
{
	return member.lineWrap;
}

void BTextEdit::setWordWrap(bool wordWrap)
{
	if (member.wordWrap != wordWrap)
	{
		member.wordWrap = wordWrap;
		for (int i = 0; i < member.blocklst.size(); i++)
		{
			member.blocklst[i].dirtyCells = true;
		}
		member.dirty = true;
		this->fresh();
	}
}
bool BTextEdit::wordWrap() const
{
	return member.wordWrap;
}

void BTextEdit::setBlinkTime(double blinkTime)
{
	if (member.blinkTime != blinkTime)
	{
		member.blinkTime = blinkTime;
		this->fresh();
	}
}
double BTextEdit::blinkTime() const
{
	return member.blinkTime;
}

void BTextEdit::setReadOnly(bool readOnly)
{
	if (member.readOnly != readOnly)
	{
		member.readOnly = readOnly;
		member.inputBlock.reset();
		member.dirty = true;
		this->fresh();
	}
}
bool BTextEdit::readOnly() const
{
	return member.readOnly;
}

bool BTextEdit::setText(const BString& text)
{
	member.text = text;
	member.initBlocks();
	member.dirty = true;
	emit(Signal_TextChanged, member.text);
	this->fresh();
	return true;
}
bool BTextEdit::addText(const BString& text)
{
	member.appendBlocks(text);
	member.dirty = true;
	emit(Signal_TextChanged, member.text);
	this->fresh();
	return true;
}
const BString& BTextEdit::text() const
{
	if (member.changed)
	{
		member.text.reset();
		for (int i = 0; i < member.blocklst.size(); i++)
		{
			ILineBlock& block = member.blocklst[i];
			member.text.append(block.text);
		}
		member.changed = false;
	}
	return member.text;
}

bool BTextEdit::setLineText(int line, const BString& text)
{
	auto it = member.blocklst.seek(line);
	if (it.empty())
		return false;
	ILineBlock& block = *it;
	int end = text.find('\n');
	text.substr(block.text, 0, end + 1);
	block.text.remove('\r');
	if (!block.text.endWith('\n'))
		block.text << '\n';
	block.reset();
	block.setFont((BFont*)this->font());
	member.dirty = true;
	emit(Signal_TextChanged, member.text);
	this->fresh();
	return true;
}
const BString& BTextEdit::lineText(int line) const
{
	return member.blocklst(line).text;
}

int BTextEdit::lineCount() const
{
	return member.blocklst.size();
}
const BRange& BTextEdit::lineRange(int line) const
{
	return member.blocklst(line).range;
}
const BRect& BTextEdit::lineRect(int line) const
{
	return member.blocklst(line).rect;
}
int BTextEdit::lineAt(const BPoint& point) const
{
	for (auto it = member.showBegin; it < member.showEnd; it++)
	{
		if (it->rect.top() <= point.y() && it->rect.bottom() >= point.y())
		{
			return it.index();
		}
	}
	return -1;
}

bool BTextEdit::setLineVisible(int line, bool visible)
{
	if (member.blocklst.check(line))
	{
		auto it = member.blocklst.seek(line);
		if (it->visible != visible)
		{
			it->visible = visible;
			member.dirty = true;
			this->fresh();
		}
		return true;
	}
	return false;
}
bool BTextEdit::lineVisible(int line) const
{
	auto it = member.blocklst.seek(line);
	if (it.valid())
		return it->visible;
	return false;
}

bool BTextEdit::setFormat(BTextFormat* format)
{
	if (member.format != format)
	{
		BTextFormatHolder temp = member.format;
		member.format = format;
		if (temp)
			format_member(temp)->setTextEdit(0);
		if (format)
			format_member(format)->setTextEdit(this);
		member.dirty = true;
		this->emit(Signal_FormatChanged, format);
		this->fresh();
		return true;
	}
	return false;
}
const BTextFormat* BTextEdit::format() const
{
	return member.format;
}
BTextFormat* BTextEdit::format()
{
	return member.format;
}

bool BTextEdit::scrollToLine(int line, Align align)
{
	if (member.blocklst.check(line))
	{
		ILineBlock& block = member.blocklst[line];
		return this->scrollToRect(block.rect, align);
	}
	return false;
}

bool BTextEdit::input(const BString& text, const BValue& value)
{
	if (text == "input-line")
	{
		int lineNumber = value;
		if (lineNumber >= 0 && lineNumber < member.blocklst.size())
		{
			member.inputBlock = member.blocklst.seek(lineNumber);
			member.inputBlock->inputIndex = 0;
			member.inputBlock->inputCount = member.inputBlock->cells.size();
			this->fresh();
			return true;
		}
		return false;
	}
	if (text == "input-index")
	{
		int inputIndex = value;
		if (member.inputBlock.valid() && inputIndex >= 0 && inputIndex < member.inputBlock->cells.size())
		{
			member.inputBlock->inputIndex = inputIndex;
			member.inputBlock->inputCount = 0;
			this->fresh();
			return true;
		}
		return false;
	}
	if (text == "input-range")
	{
		const BRange& range = value;
		if (member.inputBlock.valid() && member.inputBlock->range.contain(range))
		{
			member.inputBlock->inputIndex = range.min() - member.inputBlock->range.min();
			member.inputBlock->inputCount = range.size();
			this->fresh();
			return true;
		}
		return false;
	}
	return false;
	//return BScrollArea::input(text, value);
}
bool BTextEdit::query(const BString& text, BValue& value) const
{
	if (text == "changed")
	{
		value = member.changed;
		return true;
	}
	if (text == "input-line")
	{
		value = member.inputBlock.index();
		return true;
	}
	if (text == "input-index")
	{
		value = member.inputBlock->inputIndex;
		return true;
	}
	if (text == "input-rect")
	{
		int index = member.inputBlock->inputIndex;
		value = member.inputBlock->cells[index].rect;
		return true;
	}
	if (text == "line-range")
	{
		value = BRange(member.showBegin.index(), member.showEnd.index());
		return true;
	}
	if (text == "code-rects")
	{
		BRectArray& rects = value.fill<BRectArray>();
		rects.resize(member.text.size());
		for (auto it = member.blocklst.begin(); it.valid(); it++)
		{
			int begin = it->range.min();
			BPoint pos = it->rect.pos();
			for (int i = 0; i < it->cells.size(); i++)
			{
				CodeCell& cell = it->cells[i];
				rects[begin + i] = cell.rect + pos;
			}
		}
		return true;
	}
	if (text == "visual-lines")
	{
		BIntArray& lines = value.fill<BIntArray>();
		lines.reset();
		for (auto it = member.showBegin; it < member.showEnd; it++)
		{
			lines << it.index();
		}
		return true;
	}
	return BScrollArea::query(text, value);
}

void BTextEdit::scrollArea()
{
	member.dirty = true;
	this->fresh();
	BScrollArea::scrollArea();
}

bool BTextEdit::event(const BEvent& event)
{
	if (member.format && member.format->enabled())
	{
		format_member(member.format)->event(event);
	}
	return BWidget::event(event);
}

void BTextEdit::updateEvent(const BEvent& event)
{
	if (this->focused() && !member.readOnly)
	{
		bool inputShown = true;
		if (member.blinkTime > 0)
		{
			BReal now = event.time();
			BReal time = now - member.blinkTick;
			inputShown = time <= member.blinkTime / 2;
			if (time > member.blinkTime)
				member.blinkTick = now;
		}
		if (member.inputShown != inputShown)
		{
			member.inputShown = inputShown;
			this->fresh();
		}
	}
}
void BTextEdit::resizeEvent(const BEvent& event)
{
	BScrollArea::resizeEvent(event);
	if (member.lineWrap)
	{
		for (int i = 0; i < member.blocklst.size(); i++)
		{
			member.blocklst[i].dirtyCells = true;
		}
		member.dirty = true;
	}
}

void BTextEdit::styleEvent(const BEvent& event)
{
	BScrollArea::styleEvent(event);
	if (const BStyle* style = event.value())
	{
		const BValue& textValue = style->value(Value_Text);
		if (textValue.valid())
			member.text = (BString&)textValue;

		if (const BFont* font = this->font())
		{
			int fontHeight = font->size();
			this->verticalScrollBar()->setStep(fontHeight);
			for (int i = 0; i < member.blocklst.size(); i++)
			{
				ILineBlock& block = member.blocklst[i];
				block.setFont(font);
			}
		}

		member.dirty = true;
		this->fresh();
	}

}
void BTextEdit::freshEvent(const BEvent& event)
{
	BScrollArea::freshEvent(event);
	if (member.dirty)
	{
		member.dirty = false;
		member.freshBlocks();
		this->emit(Signal_Flushed);
	}
}
void BTextEdit::paintEvent(const BEvent& event)
{
	BPainter painter(this);
	painter.drawBackground();

	if (member.beginBlock.valid())
	{
		painter.setColor(Color_Background_Checked);
		for (auto it = member.beginBlock; it <= member.endBlock; it++)
		{
			int index = it->inputIndex;
			BPoint pos = it->rect.pos();
			for (int i = 0; i < it->inputCount; i++)
			{
				CodeCell& cell = it->cells[index + i];
				painter.fillRect(cell.rect + pos);
			}
		}
	}
	if (member.inputBlock.valid() && member.inputBlock->inputCount)
	{
		painter.setColor(Color_Background_Checked);
		int index = member.inputBlock->inputIndex;
		BPoint pos = member.inputBlock->rect.pos();
		for (int i = 0; i < member.inputBlock->inputCount; i++)
		{
			CodeCell& cell = member.inputBlock->cells[index + i];
			painter.fillRect(cell.rect + pos);
		}
	}

	for (auto it = member.showBegin; it < member.showEnd; it++)
	{
		if (it->visible)
		{
			painter.setColor(Color_Text);
			it->paint(painter, member.format);
		}
	}

	auto input = member.inputBlock;
	if (!member.readOnly && member.inputShown && input.valid() && input->inputCount == 0 && input->inputIndex >= 0)
	{
		painter.setLineWidth(2);
		painter.drawLine(member.inputLine[0], member.inputLine[1]);
	}

	painter.drawPerch();
	painter.drawBorder();
	painter.drawMargin();

	if (0)//member.hoverBlock.valid())
	{
		painter.setColor(255, 0, 0);
		if (member.hoverBlock->cells.empty())
		{
			BRect rect = member.hoverBlock->rect;
			painter.setLineWidth(1);
			painter.drawLine(rect.leftTop(), rect.leftBottom());
		}
		else if (member.hoverIndex < member.hoverBlock->cells.size())
		{
			CodeCell& cell = member.hoverBlock->cells[member.hoverIndex];
			BRect rect = cell.rect + member.hoverBlock->rect.pos();
			painter.setLineWidth(1);
			painter.drawLine(rect.leftTop(), rect.leftBottom());
		}
		else
		{
			CodeCell& cell = member.hoverBlock->cells[member.hoverIndex - 1];
			BRect rect = cell.rect + member.hoverBlock->rect.pos();
			painter.setLineWidth(1);
			painter.drawLine(rect.rightTop(), rect.rightBottom());
		}
	}
}

void BTextEdit::focusEvent(const BEvent& event)
{
	if (!this->focused())
	{
		member.inputShown = false;
		if (member.inputBlock.valid() && member.inputBlock->inputCount)
		{
			member.unselected();
			this->fresh();
		}
		if (member.edited)
		{
			const BString& text = this->text();
			emit(Signal_EditFinished, text);
		}
	}
	BScrollArea::focusEvent(event);
}
void BTextEdit::keyDownEvent(const BKeyEvent& keyEvent)
{
	if (keyEvent.keyState(Key_LCtrl))
	{
		if (keyEvent.key() == Key_C)
		{
			BString text;
			member.selectedText(text);
			bool ret = BSystem::SetClipBoardText(text);
		}
		else if (keyEvent.key() == Key_X)
		{
			BString text;
			member.selectedText(text);
			if (text.size())
			{
				bool ret0 = BSystem::SetClipBoardText(text);
				bool ret1 = member.removeSelection();
				member.inputShown = true;
				member.blinkTick = keyEvent.time() + (BReal)0.5;
			}
		}
		else if (keyEvent.key() == Key_V && member.inputBlock.valid())
		{
			BString text;
			bool ret = BSystem::GetClipBoardText(text);
			if (text.size())
			{
				bool ret0 = member.removeSelection();
				bool ret1 = member.input(text);
				member.inputShown = true;
				member.blinkTick = keyEvent.time() + (BReal)0.5;
			}
		}
	}
	else if (keyEvent.key() == Key_Left)
	{
		if (member.beginBlock.valid())
		{
			member.inputBlock->inputCount = 0;
			member.beginBlock->inputCount = 0;
			member.endBlock->inputCount = 0;
			member.beginBlock.reset();
			member.endBlock.reset();
			member.inputBlock = member.beginBlock;
		}
		else if (member.inputBlock->inputCount > 0)
		{
			member.inputBlock->inputCount = 0;
		}
		else
		{
			if (keyEvent.keyState(Key_LCtrl))
				member.seekPrevWord();
			else
				member.seekPrev();
		}
		member.inputShown = true;
		member.blinkTick = keyEvent.time() + (BReal)0.5;
		member.wrapInputLine();
	}
	else if (keyEvent.key() == Key_Right)
	{
		if (member.beginBlock.valid())
		{
			member.inputBlock->inputCount = 0;
			member.beginBlock->inputCount = 0;
			member.endBlock->inputCount = 0;
			member.beginBlock.reset();
			member.endBlock.reset();
			member.inputBlock = member.endBlock;
		}
		else if (member.inputBlock->inputCount > 0)
		{
			member.inputBlock->inputCount = 0;
		}
		else
		{
			if (keyEvent.keyState(Key_LCtrl))
				member.seekNextWord();
			else
				member.seekNext();
		}
		member.inputShown = true;
		member.blinkTick = keyEvent.time() + (BReal)0.5;
		member.wrapInputLine();
	}
	else if (keyEvent.key() == Key_Up)
	{
		member.seekUp();
		member.inputShown = true;
		member.blinkTick = keyEvent.time() + (BReal)0.5;
		member.wrapInputLine();
	}
	else if (keyEvent.key() == Key_Down)
	{
		member.seekDown();
		member.inputShown = true;
		member.blinkTick = keyEvent.time() + (BReal)0.5;
		member.wrapInputLine();
	}
	else if (keyEvent.key() == Key_Tab)
	{
		member.input(BCode('\t'));
		member.inputShown = true;
		member.blinkTick = keyEvent.time() + (BReal)0.5;
	}
	else if (keyEvent.key() == Key_Enter)
	{
		member.input(BCode('\n'));
		member.inputShown = true;
		member.blinkTick = keyEvent.time() + (BReal)0.5;
	}
	else if (keyEvent.key() == Key_Delete)
	{
		if (!member.removeSelection())
		{
			member.removeIndex();
		}
		member.inputShown = true;
		member.blinkTick = keyEvent.time() + (BReal)0.5;
	}
	if (keyEvent.key() == Key_Back)
	{
		if (!member.removeSelection())
		{
			if (member.seekPrev())
			{
				member.removeIndex();
			}
		}
		member.inputShown = true;
		member.blinkTick = keyEvent.time() + (BReal)0.5;
	}
	BScrollArea::keyDownEvent(keyEvent);
}
void BTextEdit::keyUpEvent(const BKeyEvent& keyEvent)
{
	BScrollArea::keyUpEvent(keyEvent);
}
void BTextEdit::charEvent(const BCharEvent& charEvent)
{
	BScrollArea::charEvent(charEvent);
	if (member.inputBlock.valid())
	{
		const BCode& code = charEvent.code();
		member.removeSelection();
		member.input(code);
	}
}

void BTextEdit::mouseEnterEvent(const BMouseEvent& mouseEvent)
{
	BScrollArea::mouseEnterEvent(mouseEvent);
}
void BTextEdit::mouseLeaveEvent(const BMouseEvent& mouseEvent)
{
	BScrollArea::mouseLeaveEvent(mouseEvent);
}

void BTextEdit::mousePressEvent(const BMouseEvent& mouseEvent)
{
	BPoint pos = mouseEvent.pos();
	if (mouseEvent.button() == Button_Left)
	{
		if (mouseEvent.keyState(Key_LCtrl))
		{
			member.inputBlock = member.hoverBlock;
			member.inputBlock->selectWord();
		}
		else if(mouseEvent.keyState(Key_LShift))
		{
			member.selectBlocks();
		}
		else
		{
			member.unselected();
			if (this->rect(Part_Left).contain(pos))
			{
				member.inputBlock = member.hoverBlock;
				if (member.inputBlock.valid())
				{
					member.inputBlock->inputIndex = 0;
					member.inputBlock->inputCount = member.inputBlock->cells.size();
				}
			}
			else
			{
				member.inputBlock = member.hoverBlock;
				if (member.inputBlock.valid())
				{
					member.inputBlock->inputIndex = member.hoverIndex;
					member.inputBlock->inputCount = 0;
					member.freshInputLine();
				}
				member.inputShown = true;
				member.blinkTick = mouseEvent.time() + (BReal)0.5;
			}
			member.pressed = true;
			member.pressBlock = member.hoverBlock;
			member.pressIndex = member.hoverIndex;
		}
		this->fresh();
	}
	BScrollArea::mousePressEvent(mouseEvent);
}
void BTextEdit::mouseReleaseEvent(const BMouseEvent& mouseEvent)
{
	if (mouseEvent.button() == Button_Left)
	{
		if (member.pressed)
		{
			member.pressed = false;
		}
	}
	BScrollArea::mouseReleaseEvent(mouseEvent);
}
void BTextEdit::mouseMoveEvent(const BMouseEvent& mouseEvent)
{
	BPoint pos = mouseEvent.pos();

	ILineBlockIterator hoverBlock = member.blocklst.rbegin();
	int	hoverIndex = hoverBlock->cells.size();
	for (auto it = member.showBegin; it < member.showEnd; it++)
	{
		const BRect& rect = it->rect;
		if (rect.top() <= pos.y() && rect.bottom() >= pos.y())
		{
			hoverBlock = it;
			hoverIndex = hoverBlock->indexAt(pos);
			break;
		}
	}
	if (hoverBlock->cells(hoverIndex - 1) == '\n')
		hoverIndex--;
	if (member.hoverBlock != hoverBlock || member.hoverIndex != hoverIndex)
	{
		member.hoverBlock = hoverBlock;
		member.hoverIndex = hoverIndex;
	}
	if (!member.readOnly)
	{
		const BRect& crect = this->centerRect();
		if (crect.contain(pos))
			this->setCursor(Cursor_Ibeam);
		else
			this->setCursor(Cursor_None);
	}
	if (member.pressed)
	{
		member.selectBlocks();
		this->fresh();
	}
	BScrollArea::mouseMoveEvent(mouseEvent);
}
void BTextEdit::mouseDoubleClickEvent(const BMouseEvent& mouseEvent)
{
	if (mouseEvent.button() == Button_Left)
	{
		if (member.inputBlock.valid())
		{
			member.inputBlock->selectWord();
			member.pressBlock = member.inputBlock;
			this->fresh();
		}
	}
	BScrollArea::mouseDoubleClickEvent(mouseEvent);
}
